#pragma once
#include <iostream>
#include <atomic>
#include <deque>
#include <mutex>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>

using boost::asio::ip::tcp;
using boost::asio::ip::udp;
using boost::asio::serial_port;
using boost::asio::ip::address;

class Session : public boost::enable_shared_from_this<Session>
{
public:
	Session(boost::asio::io_service& io_service, const std::string& name)
		: mIOService(io_service), mName(name) {}

	virtual ~Session();

	const std::string& GetName() { return mName; }

	void SetConnectStr(const std::string& connstr) { mConnStr = connstr; }

	virtual bool Connect();

	virtual void Close() {};

	virtual bool IsConnected() { return false; };

	virtual bool SendSome(const std::string& message);

	size_t GetMessageCount() const { return mMessageQueue.size(); }

	std::string GetMessage();

public:
	virtual bool DoCreateSocketAndConnect(const std::string& connstr) = 0;

	virtual void AsyncReadSome() = 0;

	void handle_read(const boost::system::error_code& error, size_t bytes_transferred);

	virtual bool DoSendSome(const std::string& message) = 0;

protected:
	void Record(const std::string& message, bool isrecv, bool success);

protected:
	boost::asio::io_service& mIOService;
	enum { max_length = 1024 };
	char mRecvBuf[max_length];
	std::string mName;
	std::string mConnStr;
	std::deque<std::string> mMessageQueue;
	std::mutex mLock;

	static std::string mRecordDir;
	static std::atomic_bool mEnableRecord;
};

template<typename Socket>
class SocketSession : public Session
{
public:
	SocketSession(boost::asio::io_service& io_service, const std::string& name)
		: Session(io_service, name) {}

	virtual ~SocketSession() {}

	void Close()
	{
		if (mSocket)
		{
			boost::system::error_code ec;
			mSocket->close(ec);
			delete mSocket;
			mSocket = nullptr;
		}
	}

	bool IsConnected() { return mSocket != nullptr; }

// 	bool SendSome(const std::string& message)
// 	{
// 		//return boost::asio::write(*mSocket, boost::asio::buffer(message.data(), message.length())) == message.length();
// 	}

protected:

// 	void AsyncReadSome()
// 	{
// // 		boost::asio::async_read(*mSocket, mRecvBuf,
// // 			boost::bind(&Session::handle_read, shared_from_this(),
// // 				boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
// 	}

protected:
	Socket* mSocket = nullptr;
};

class UDPSession : public SocketSession<udp::socket>
{
public:
	UDPSession(boost::asio::io_service& io_service, const std::string& name)
		: SocketSession<udp::socket>(io_service, name) {}

protected:
	bool DoCreateSocketAndConnect(const std::string& connstr);

	void AsyncReadSome();

	bool DoSendSome(const std::string& message);

private:
	udp::endpoint sender_endpoint_;
};

template<typename Socket>
class GeneateSession : public SocketSession<Socket>
{
public:
	GeneateSession(boost::asio::io_service& io_service, const std::string& name)
		: SocketSession<Socket>(io_service, name) {}

	virtual ~GeneateSession() {}

	void Close()
	{
		if (mSocket)
		{
			boost::system::error_code ec;
			mSocket->close(ec);
			delete mSocket;
			mSocket = nullptr;
		}
	}

	bool IsConnected() { return mSocket != nullptr; }

protected:

	void AsyncReadSome()
	{
	 	boost::asio::async_read(*mSocket, boost::asio::buffer(mRecvBuf, max_length),
	 		boost::bind(&Session::handle_read, shared_from_this(),
	 			boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
	}

	bool DoSendSome(const std::string& message)
	{
		return boost::asio::write(*mSocket, boost::asio::buffer(message.data(), message.length())) == message.length();
	}

protected:
	Socket* mSocket = nullptr;
};

class TCPSession : public GeneateSession<tcp::socket>
{
public:
	TCPSession(boost::asio::io_service& io_service, const std::string& name)
		: GeneateSession<tcp::socket>(io_service, name) {}

protected:
	bool DoCreateSocketAndConnect(const std::string& connstr);

private:
	tcp::endpoint server_endpoint;
};

class SerialSession : public GeneateSession<boost::asio::serial_port>
{
public:
	SerialSession(boost::asio::io_service& io_service, const std::string& name)
		: GeneateSession<boost::asio::serial_port>(io_service, name) {}

protected:
	bool DoCreateSocketAndConnect(const std::string& connstr);
};

typedef boost::shared_ptr<Session> SessionPtr;